Skip to main content

Adding New Contacts

In this step, we'll implement a modal that allows users to add new contacts by alias within the app. This process includes initializing the agent, connecting it to WebSocket (WS) sessions, and adding contacts to the app using the One37ID SDK.

Objective

  • Set up WebSocket sessions for all existing connections when the app starts.
  • Add new contacts through a modal by their alias.

1. Initializing the Agent in App.js or App.tsx

To ensure that WebSocket sessions are established for existing connections, you need to initialize the agent and start the WebSocket connections when the app starts. Add the following code to App.js or App.tsx:

import { initializeAgent } from "./src/one37Agent";
import crypto from "@sphereon/isomorphic-webcrypto";

useEffect(() => {
const startAllWSSessions = async () => {
const agent = await initializeAgent();
if (!agent) {
throw new Error("Agent not initialized");
}

const connections = await agent.contactManager.getList({
currentPage: 1,
rowsPerPage: 1000,
});
if (connections.result) {
for (let connection of connections.result) {
await agent.contactManager.connectWS({ id: connection.id });
}
}
};

startAllWSSessions();
}, []);

useEffect(() => {
crypto.ensureSecure(); // Ensure secure crypto environment
}, []);

This block:

  • Initializes the agent when the app starts.
  • Retrieves existing contacts and establishes WebSocket sessions for each.

2. Adding Crypto to package.json

To ensure compatibility with the One37ID SDK and React Native's environment, ensure the following configuration to package.json if they dosen't exists:

"browser": {
"crypto": "@sphereon/isomorphic-webcrypto"
}

And in react-native:

"react-native": {
"crypto": "@sphereon/isomorphic-webcrypto"
}

This ensures that crypto functions correctly in the app.

Run yarn install afterwards.

3. Creating the Add Contact Modal

Now, we'll create a modal that enables users to add new contacts by their alias.

import React, { useState } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
ActivityIndicator,
} from "react-native";
import Modal from "react-native-modal";
import { initializeAgent } from "../../one37Agent";
import { Contact, OperationDataResult } from "@one37id/mobile-js-sdk";

type AddContactModalProps = {
modalVisible: boolean,
setModalVisible: (visible: boolean) => void,
onContactAdded: () => void,
};

export const CustomAddContactModal = ({
modalVisible,
setModalVisible,
onContactAdded,
}: AddContactModalProps) => {
const [alias, setAlias] = useState("");
const [isLoading, setIsLoading] = useState(false);

const addByAlias = async () => {
if (alias.trim() === "") {
alert("Alias cannot be empty");
return;
}
setIsLoading(true);
try {
const res = await createContact(alias);
onContactAdded();
console.log("Contact added successfully:", res);
} catch (error) {
console.error("Error adding contact:", error);
} finally {
setIsLoading(false);
setModalVisible(false);
}
};

const createContact = async (alias: string) => {
const agent = await initializeAgent();
if (!agent) {
throw new Error("Agent not initialized");
}
return agent.contactManager
.addByAlias({ realm: alias })
.then((contact: OperationDataResult<Contact>) => {
if (!contact.isSuccessful) {
console.error(
`Failed to create contact for alias: ${alias}, error: ${contact.error}`
);
return Promise.reject(new Error(`${contact.error}`));
}
return contact.result;
})
.catch((error) => {
console.error(`Error while adding contact: ${error.message}`);
return Promise.reject(
new Error(`Unable to create contact. Error: ${error.message}`)
);
});
};

return (
<>
<Modal
isVisible={modalVisible}
onBackdropPress={() => setModalVisible(false)}
animationIn="slideInUp"
animationOut="slideOutDown"
backdropColor="rgba(0,0,0,0.5)"
backdropOpacity={0.7}
style={styles.modalContainer}
>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Add New Contact</Text>
<TextInput
style={styles.input}
placeholder="Enter Alias"
value={alias}
onChangeText={setAlias}
/>
<TouchableOpacity style={styles.addButton} onPress={addByAlias}>
<Text style={styles.addButtonText}>Add Contact</Text>
</TouchableOpacity>
</View>
</Modal>
{isLoading && (
<ActivityIndicator
style={styles.loading}
size="large"
color="#00ff00"
/>
)}
</>
);
};

const styles = StyleSheet.create({
modalContainer: {
justifyContent: "center",
margin: 0,
},
modalContent: {
backgroundColor: "white",
padding: 20,
borderRadius: 10,
alignItems: "center",
justifyContent: "center",
},
modalTitle: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 15,
},
input: {
width: "100%",
height: 40,
borderColor: "#ccc",
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 20,
},
addButton: {
backgroundColor: "#008080",
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
alignItems: "center",
},
addButtonText: {
color: "#fff",
fontWeight: "bold",
},
loading: {
position: "absolute",
top: "50%",
left: "50%",
zIndex: 1,
},
});

When initializing the agent, we need to include a callbackHandlers object. This object will hold the necessary callback functions, such as the one used for handling contact approvals.

Here's how we set the addContactApprovalCallback:

const callbackHandlers = {
addContactApprovalCallback: handleAddContactApproval,
};

Define the handleAddContactApproval Function

The handleAddContactApproval function is a key part of this flow. It gets triggered when a new contact is added by alias. This function navigates to a screen where the user can either approve or decline the contact request. Here's a simplified version of the function that handles contact approvals.

async function handleAddContactApproval(
contactArgs: NewContactInfo
): Promise<boolean> {
console.log("---handleAddContactApproval:", contactArgs);

return new Promise((resolve, reject) => {
try {
//Navigate to the add contact screen
RootNavigation.navigate("ContactAddScreen", {
contactData: contactArgs,
onDecline: () => {
console.log("---Contact declined");
resolve(false); // Return `false` to indicate the contact was not approved
},
onCreate: () => {
console.log("---Contact accepted");
resolve(true); // Return `true` to indicate the contact was approved
},
});
} catch (error) {
console.log("---Error during handleAddContactApproval:", error);
reject(error); // Handle any errors that may occur during navigation
}
});
}

Key Points

  • Navigation to Approval Screen: The function uses RootNavigation.navigate to take the user to the ContactAddScreen where they can approve or decline the contact.
  • Callbacks for Approval/Decline: When the user accepts or rejects the contact, the respective callback is triggered (onCreate for approval and onDecline for rejection).

Implement the ContactAddScreen for Approval

The ContactAddScreen is the UI where users can view details about the contact and choose to either approve or decline the contact request. Here’s a simple implementation:

import React, { FC } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import FastImage from "react-native-fast-image";

// Define your stack params to type-check navigation params
type StackParamList = {
ContactAddScreen: {
contactData: { title: string, contactType: string },
onCreate: () => void,
onDecline: () => void,
},
};

type Props = NativeStackScreenProps<StackParamList, "ContactAddScreen">;

const ContactAddScreen: FC<Props> = ({ route, navigation }) => {
const { contactData, onCreate, onDecline } = route.params;
console.log("params", route.params);
console.log("contactData:", contactData);
const handleCreate = async () => {
if (onCreate) {
await onCreate();
navigation.goBack();
}
};

const handleDecline = async () => {
if (onDecline) {
await onDecline();
navigation.goBack();
}
};

return (
<View style={styles.container}>
{/* Header Section with Title and Image */}
<View style={styles.headerContainer}>
<Text style={styles.title}>Connection Request</Text>
{contactData?.logoUrl ? (
<FastImage
source={{ uri: contactData.logoUrl }}
style={styles.logoImage}
resizeMode={FastImage.resizeMode.contain} // Use FastImage's resizeMode
/>
) : null}
</View>

{/* Details Section */}
<View style={styles.details}>
<Text style={styles.contactTitle}>{contactData?.title}</Text>
<Text style={styles.contactType}>Type: {contactData?.contactType}</Text>
</View>

{/* Action Buttons */}
<View style={styles.buttonsContainer}>
<TouchableOpacity style={styles.button} onPress={handleDecline}>
<Text style={styles.buttonText}>Reject</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={handleCreate}>
<Text style={styles.buttonText}>Accept</Text>
</TouchableOpacity>
</View>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
headerContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
title: {
fontSize: 22,
fontWeight: "bold",
},
logoImage: {
width: 60,
height: 60,
marginRight: 20,
},
details: {
marginTop: 20,
},
contactTitle: {
fontSize: 18,
fontWeight: "600",
},
contactType: {
fontSize: 16,
color: "gray",
marginTop: 5,
},
buttonsContainer: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 30,
},
button: {
backgroundColor: "#007BFF",
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
},
buttonText: {
color: "#fff",
fontSize: 16,
},
});

export default ContactAddScreen;

Explanation of the ContactAddScreen

  • Header Section: Displays the connection request title and logo (if available).
  • Details Section: Shows the contact's title and type.
  • Action Buttons: The user can either "Accept" or "Reject" the contact request by clicking the respective button.
    • Accept: Triggers the onCreate callback and resolves the promise.
    • Reject: Triggers the onDecline callback and resolves the promise with a rejection.

Using the Setup

Now, whenever a new contact is added by alias, the handleAddContactApproval function will trigger the navigation to the approval screen, allowing the user to review and accept or decline the contact request.

This flow provides a user-friendly way for your app to handle new contact requests, making it simple for developers to customize and expand upon in their projects.

Breakdown of the CustomAddContactModal:

  • Alias Input: The user enters the alias of the new contact in a TextInput.
  • Add Button: The button triggers the addByAlias function, which interacts with the One37ID SDK to create a new contact.
  • Loading State: The modal shows a loading indicator while the contact is being added.

Key Points:

  1. Agent Initialization: Before creating a new contact, the agent is initialized using initializeAgent.
  2. Contact Creation: The addByAlias function uses the SDK’s contactManager to create a new contact based on the provided alias.
  3. Modal Visibility: The modal visibility is controlled by the setModalVisible function, and the modal closes once the contact is added.

With this setup, your app allows users to add new contacts quickly and efficiently using an alias, while ensuring secure communication via WebSockets and the One37ID agent.

Final Thoughts

At this point, you have a fully functional screen that displays the user’s contacts. Users can see all their connections, view details, and even add new contacts.

This simple contacts screen is a key part of any app that interacts with the One37ID platform, allowing users to manage their secure connections easily.

X

Graph View